﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;
using System.Diagnostics;

namespace DirectX_CS
{
	public partial class Camera : Form , INamed	, IDisposable, IRangeable
	{
		float boxSize = 10f;
		[PGBrowsable]
		public float BoxSize
		{
			get { return boxSize; }
			set { boxSize = value; }
		}

		
		[PGBrowsable]
		public Range3d Range
		{
			get {
				Vector3 p = mCameraPosition;
				float s = boxSize;
				return new Range3d(p.X - s, p.Y - s, p.Z - s, p.X+s, p.Y + s, p.Z+s); 
			}
		}
        //private Cartesian3dCircle mDataCameraCircle = null;
        //private Cartesian3dAxis mDataCameraAxis = null;

        //protected static ManagedRenderer mRenderer = null;
        //MainProgram mForm = null;

        //get camera spherical coordinates in terms of camera target 
        private Vector3 mCameraPosition;
        private Vector3 mCameraTarget;
        private Vector3 mUpVector;

		protected bool initializing = true;
		protected string shortName = "Camera";
		[PGBrowsable]
		public string ShortName
		{
			get { return shortName; }
			set { shortName = value; }
		}
		private int shortNumber = -1;

		public int ShortNumber
		{
			get { return shortNumber; }
			set { shortNumber = value; }
		}
		[PGBrowsable]
		public string ObjectName
		{
			get { return shortName + " " + shortNumber; }
		}

		private static List<Camera> mForms;
		~Camera()
		{
			DisposeInstance();
		}

		public void DisposeInstance()
		{
			Mouse.MouseEvent -= new MouseEventHandler(Mouse_MouseEvent);
			mForms.Remove(this);
			this.Dispose();
		}
		private static int mCameraCount = 0;
		protected static int GetNewCameraNumber()
		{
			mCameraCount++;
			return mCameraCount;
		}
        public Camera()
        {
			//set the camera internal number in memory
			mCameraNumber = GetNewCameraNumber();
			this.shortNumber = mCameraNumber;
			this.Text = string.Format("Camera {0}", CameraNumber);

			initializing = true;

            InitializeComponent();

			//CameraTarget = new Vector3(0, 0, 0);
			//RelativeCameraPosition = new Vector3(25, 25, 5);
			Camera theForm = Camera.Selected;
			if (theForm == null)
			{
				CameraTarget = new Vector3(0, 0, 0); 
				CameraPosition = new Vector3(500, 500, 50); 
				CameraUpVector = new Vector3(0, 0, 1); 
			}
			else
			{
				CameraTarget = theForm.CameraTarget;
				CameraPosition = theForm.CameraPosition;
				CameraUpVector = theForm.CameraUpVector;
			}
			UpdateAllControlValues();
             //register both..
            //mDataCameraAxis = new Cartesian3dAxis( Device, mRenderer, -10, 10, -10, 10, -10, 10, 10, 10, 10 );
            //mDataCameraCircle = new Cartesian3dCircle(Device, mRenderer, (float)20, 0, Color.CornflowerBlue);

            //ResetCamera();
			initializing = false;

			if (mForms == null)
			{
				mForms = new List<Camera>();
				defaultCamera = this;
			}

			mForms.Add(this);
			selected = this;

			//initialize the light number to move with the camera
			nudLightToMove.Maximum = MainProgram.DxDevice.Lights.Count - 1; 
			nudLightToMove.Minimum = 0;
			nudLightToMove.Increment = 1;
			nudLightToMove.Value = 0;

			Mouse.MouseEvent += new MouseEventHandler(Mouse_MouseEvent);	
			//finally dont forget to set the light cones
			

        }

		public void UpdateAllControlValues()
		{
			UpdateCameraTargetControls(CameraTarget);
			UpdateCameraPositionControls(CameraPosition);
			UpdateCameraUpControls(CameraUpVector);
		}
		protected int mCameraNumber;
		[PGBrowsable]
		public int CameraNumber
		{
			get { return mCameraNumber; }
			set { mCameraNumber = value; }
		}
		[PGBrowsable]
		public string DisplayName
		{
			get { return "Camera " + CameraNumber + 
				((DefaultCamera == this) ? "(Default)" : "");}
		}
		private static Camera defaultCamera = null;
		protected static Device Device
		{
			get { return MainProgram.DxDevice ; }
		}
		public void SetCamera(Vector3 target, Vector3 position)
		{
			CameraTarget = target; CameraPosition = position;
			//SetViewTransform();
		}

		public static Camera DefaultCamera
		{
			get
			{
				if (defaultCamera == null)
				{
					defaultCamera = new Camera();
					
					ObjectsEditor.ReloadList();
				}
				return Camera.defaultCamera; 
			}
			set { Camera.defaultCamera = value; }
		}
		//private double cameraPhi = (float)(Math.PI / 4); //45 degrees
		//private double cameraRho = 100f;
		//private double cameraTheta = 0f; //theta
		//private double distanceOfCameraFromLookAtPoint = 35f; //rho...

		public float CameraRho
		{
			get { return (mCameraPosition - mCameraTarget).Length(); }
		}
		public Vector3 Direction
		{
			get { return mCameraTarget - mCameraPosition; }
		}
		public static List<Camera> CameraForms
		{
			get {
				if (mForms == null)
					return null;
				else
					return Camera.mForms.ToList<Camera>();	  //make a copy
			}
		}

		public Spherical3 GetSpherical()
		{
			Spherical3 sph = new Spherical3();

			Vector3 p = CameraPosition - CameraTarget;
			double s = p.Length();//(float)Math.Sqrt(p.X * p.X + p.Y * p.Y);
			double cameraTheta, cameraPhi, cameraRho;
			if (s != 0)
			{
				double yovers = p.Y / s;
				cameraTheta = (p.X >= 0) ? Math.Asin(p.Y / s) : Math.PI - Math.Asin(p.Y / s);
				cameraPhi = (float)Math.Acos(p.Z / s);
				cameraRho = s;
				//RelativeCameraPosition = p;	
				sph.Rho = cameraRho;
				sph.Phi = cameraPhi;
				sph.Theta = cameraTheta;
				return sph;
			}
			else
				return sph;
			
		}
		public Spherical3 CameraPositionSpherical()
		{
			return CartesianToSpherical(CameraPosition);
		}



		//private float mTRDataCameraRho = 10;
		//private float mTRDataCameraTheta = 0;
		//private float mTRDataCameraPhi = 0;
		//private float mTRDataCameraRadius;
		//private float mTRDataCameraZ;

		//float dAngle = (float)(2 * Math.PI / 360);
		//bool angleForwards = true;
		//float angleMax = float.MaxValue;
		////float angleMin = 0;


		public static Vector3 SelectedCameraPosition
		{

			set { Selected.CameraPosition = value; }
			get { return Selected.CameraPosition; }
		}
		static Camera selected = null;

		public static Camera Selected
		{
			get { return selected; }
			set { selected = value; }
		}
		public static  Vector3 SelectedCameraTarget
		{

			set { Selected.CameraTarget = value; }
			get { return Selected.CameraTarget; }
		}

		public static Vector3 SelectedCameraUpVector
		{

			set { Selected.CameraUpVector = value; }
			get { return Selected.CameraUpVector; }
		}
		
        public Vector3 CameraPosition
        {
            get { return mCameraPosition; }
            set { 
                mCameraPosition = value;

				//if (!initializing)
				//{
				//    UpdateCameraPositionControls(value);
				//}
                //mDataCameraCircle.CreateCircleOverXY((float)(RelativeCameraPosition - CameraTarget).Length(), mTRDataCameraZ, Color.LightGreen);
                //ResetCamera();
                //SetViewTransform();
            }
        }
		public void UpdateCameraPositionControls()
		{
			UpdateCameraPositionControls(mCameraPosition);
		}
		private Vector3 UpdateCameraPositionControls(Vector3 cameraPosition)
		{
			initializing = true;
			nudCPX.Value = (decimal)cameraPosition.X;
			nudCPY.Value = (decimal)cameraPosition.Y;
			nudCPZ.Value = (decimal)cameraPosition.Z;

			Spherical3 spherical = CartesianToSpherical(cameraPosition);
			nudCPRho.Value = (decimal)spherical.Rho;
			nudCPTheta.Value = (decimal)((double)spherical.Theta * 180d / Math.PI);
			nudCPPhi.Value = (decimal)((double)spherical.Phi * 180d / Math.PI);

			SetCPProjectionNUDValues();

			initializing = false;
			return cameraPosition;
		}

        public Vector3 CameraTarget
        {
            get { return mCameraTarget; }
            set {
				mCameraTarget = value;

				//if (!initializing)
				//{
				//    UpdateCameraTargetControls();
				//}
				//even if initializing... no events are currently wired up which could triggger a cascade event thingy.

				//SetTargetProjectionNUDValues();
				//UpdateCameraTargetControls(mCameraTarget);
            }
        }


		private void SetCTProjectionNUDValues()
		{
			initializing = true;
			Vector3 prj = CameraTargetProjection;
			nudProjectTX.Value = (decimal)prj.X;
			nudProjectTY.Value = (decimal)prj.Y;
			nudProjectTZ.Value = (decimal)prj.Z;
			initializing = false;
		}
		private void SetCPProjectionNUDValues()
		{
			initializing = true;
			Vector3 prj = CameraPositionProjection;
			nudProjectPX.Value = (decimal)prj.X;
			nudProjectPY.Value = (decimal)prj.Y;
			nudProjectPZ.Value = (decimal)prj.Z;
			initializing = false;
		}
		public static Vector3 Projection(Vector3 PointNearOrFar)
		{
			Device dev = MainProgram.DxDevice;
			Matrix world = Matrix.Translation(new Vector3(0, 0, 0));// dev.Transform.World;
			Form f = MainProgram.OnlyProgram.WindowsForm;
			Size s = f.ClientSize;
			//Vector3 nearsource = new Vector3((float)mouseX /s.Width, (float)mouseY / s.Height, 0f);
			float x = (float)PointNearOrFar.X ; // / (float)s.Width;
			float y = (float)PointNearOrFar.Y;
			Vector3 source = new Vector3(x, y, (float)PointNearOrFar.Z);
			//Vector3 nearPoint = nearsource; nearPoint.Unproject(dev.Viewport, dev.Transform.Projection, dev.Transform.View, world);
			Vector3 srcPnt = source; srcPnt.Project(dev.Viewport, dev.Transform.Projection, dev.Transform.View, world);
			return srcPnt;

		}

		[PGBrowsable]
		public Vector3 CameraTargetProjection
		{
			get { return Projection(CameraTarget); }
		}
		[PGBrowsable]
		public Vector3 CameraPositionProjection
		{
			get { return Projection(CameraPosition); }
		}

		MouseEventArgs lastEventArgs = null;

		void Mouse_MouseEvent(object sender, MouseEventArgs e)
		{
			if (lastEventArgs == null)
			{
				//this event cannot be a mouse up or drag because mouse down hasnt yet occurred
			}
			else
			{
				if (cbMoveLightWithCamera.Checked)
					SetLightPositionAndDirection();
			}

			lastEventArgs = e;
		}

		private void SetLightPositionAndDirection()
		{
			int SelectedLightIndex = (int)nudLightToMove.Value;
			Device dev= MainProgram.DxDevice;	

			dev.Lights[SelectedLightIndex].Position = this.CameraPosition;

			if (cbChangeLightDirection.Checked) //change direction of the light to camera direction too..
			{
				dev.Lights[SelectedLightIndex].Direction = this.Direction;
			}

			dev.Lights[SelectedLightIndex].Update(); 			
			if (Lighting.OnlyForm != null)
				Lighting.OnlyForm.LoadSelectedLight();
			//Lighting.SetLightCone(SelectedLightIndex);
		} 

		void UpdateCameraTargetControls()
		{
			UpdateCameraTargetControls(mCameraTarget);
		}
		private Vector3 UpdateCameraTargetControls(Vector3 value )
		{
			initializing = true;
			nudCTX.Value = (decimal)value.X;
			nudCTY.Value = (decimal)value.Y;
			nudCTZ.Value = (decimal)value.Z;
			Spherical3 spherical = CartesianToSpherical(value); //return new Vector3((float)cameraTheta, (float)cameraPhi, (float)cameraRho);
			nudCPRho.Value = (decimal)spherical.Rho;
			nudCPTheta.Value = (decimal)(((double)(spherical.Theta)) * 180d / Math.PI);
			nudCPPhi.Value = (decimal)(((double)(spherical.Phi)) * 180d / Math.PI);

			SetCTProjectionNUDValues();

			initializing = false;
			return value;

		}
        public Vector3 CameraUpVector
        {
            get { return mUpVector; }
            set {
				mUpVector = value;

				if (!initializing)
				{
					value = UpdateCameraUpControls(value);
				}
			}
        }

		public void UpdateCameraUpControls()
		{
			UpdateCameraUpControls(mUpVector);
		}
		private Vector3 UpdateCameraUpControls(Vector3 value)
		{
			initializing = true;

			nudUpX.Value = (decimal)value.X;
			nudUpY.Value = (decimal)value.Y;
			nudUpZ.Value = (decimal)value.Z;

			Spherical3 spherical = CartesianToSpherical(value); //return new Vector3((float)cameraTheta, (float)cameraPhi, (float)cameraRho);
			nudUVRho.Value = (decimal)spherical.Rho;
			nudUVTheta.Value = (decimal)(((double)spherical.Theta) * 180d / Math.PI);
			nudUVPhi.Value = (decimal)(((double)spherical.Phi) * 180d / Math.PI);
			initializing = true;
			return value;
		}

        public static Spherical3 CartesianToSpherical(Vector3 p)
		{
			double s = p.X * p.X + p.Y * p.Y;
			double rho = (double)p.Length(); //...sqrt p.X * p.X + p.Y * p.Y + p.Z * p.Z; 
			double phi = Math.Acos(p.Z / rho);
			double theta;
			if ( s == 0)
			{
				rho = 0; phi = 0; theta = 0; 
			}
			else if (p.X > 0 )
			{
				theta = Math.Asin(p.Y / s);
			}
			else
			{
				theta = Math.PI - Math.Asin(p.Y/s);
			}
			Spherical3 theCoord = new Spherical3();
			theCoord.Rho = rho; theCoord.Theta = theta; theCoord.Phi = phi;
			return theCoord;
        }

        public static Vector3 SphericalToCartesian(double Theta, double Phi, double rho)
        {
			double rhoSinPhi =  rho * Math.Sin( Phi );
			float x = (float)(Math.Cos(Theta) * rhoSinPhi); //ref: http://www.math.montana.edu/frankw/ccp/multiworld/multipleIVP/spherical/body.htm
			float y = (float)(Math.Sin(Theta) * rhoSinPhi);
            float z = (float)(rho * Math.Cos( Phi ));
            return new Vector3( x, y, z );
        }

        public void SetViewTransform()
        {
            Device.Transform.View = Matrix.LookAtLH(CameraPosition, CameraTarget, mUpVector);

        }

		private void CameraForm_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (e.CloseReason == CloseReason.UserClosing)
			{
				this.Hide();
				e.Cancel = true;
			}
		}

		private void nudCamera_ValuesChanged(object sender, EventArgs e)
		{
			if (!initializing)
			{
				DoLook();
			}
		}

		public void DoLook()
		{
			mCameraTarget = new Vector3((float)nudCTX.Value, (float)nudCTY.Value, (float)nudCTZ.Value); //numeric up down look at point...
			mCameraPosition = new Vector3((float)nudCPX.Value, (float)nudCPY.Value, (float)nudCPZ.Value); //numeric up down camera pos
			mUpVector = new Vector3((float)nudUpX.Value, (float)nudUpY.Value, (float)nudUpZ.Value);
			initializing = true;
			SetViewTransform();
			initializing = false;
		}
		private void btnSelect_Click(object sender, EventArgs e)
		{
			Camera.Selected = this;
			this.SetViewTransform();
			DoLook();
		}

		private void btnFocusCameraOnLight_Click(object sender, EventArgs e)
		{
			int lightNumber = (int)nudLightToMove.Value;
			Device d = MainProgram.DxDevice;
			if (lightNumber >= 0 && lightNumber < d.Lights.Count)
			{
				Camera c = this;
				c.CameraTarget = d.Lights[lightNumber].Position;
				c.SetViewTransform();
			}
		}

		private void nudCPSphericalAll_ValueChanged(object sender, EventArgs e)
		{
			if (!initializing)
			{
				Vector3 cartesian = SphericalToCartesian(
					(double)nudCPTheta.Value * Math.PI / 180d, 
					(double)nudCPPhi.Value * Math.PI / 180d, 
					(double)nudCPRho.Value );
				mCameraPosition = cartesian;
				nudCPX.Value = (decimal)cartesian.X;
				nudCPY.Value = (decimal)cartesian.Y;
				nudCPZ.Value = (decimal)cartesian.Z;
				this.SetViewTransform();
			}
		}

		private void nudCTSphericalAll_ValueChanged(object sender, EventArgs e)
		{
			if (!initializing)
			{
				Vector3 cartesian = SphericalToCartesian((double)nudCTTheta.Value * Math.PI / 180d, (double)nudCTPhi.Value * Math.PI / 180d, (double)nudCTRho.Value );
				this.CameraTarget = cartesian;
				UpdateCameraTargetControls(cartesian);
				SetCTProjectionNUDValues();
				SetLookAtPointNUDValues(cartesian);
				this.SetViewTransform();
			}

		}

		private Vector3 SetLookAtPointNUDValues(Vector3 cartesian)
		{
			nudCTX.Value = (decimal)cartesian.X;
			nudCTY.Value = (decimal)cartesian.Y;
			nudCTZ.Value = (decimal)cartesian.Z;
			return cartesian;
		}

		private void nudUVSphericalAll_ValueChanged(object sender, EventArgs e)
		{
			if (!initializing)
			{
				initializing = true;

				Vector3 cartesian = SphericalToCartesian((double)nudUVTheta.Value * Math.PI / 180d, (double)nudUVPhi.Value * Math.PI / 180d, (double)nudUVRho.Value );
				this.CameraUpVector = cartesian;


				nudUpX.Value = (decimal)cartesian.X;
				nudUpY.Value = (decimal)cartesian.Y;
				nudUpZ.Value = (decimal)cartesian.Z;

				this.SetViewTransform();
				initializing = false;
			}
		}

		private void cbMoveLightWithCamera_CheckedChanged(object sender, EventArgs e)
		{
			cbChangeLightDirection.Enabled = cbMoveLightWithCamera.CheckState == CheckState.Checked;
			SetLightPositionAndDirection();
		}

		private void btnSwapPositions_Click(object sender, EventArgs e)
		{
			decimal cpX = nudCPX.Value, cpY = nudCPY.Value, cpZ = nudCPZ.Value, 
				ctX = nudCTX.Value, ctY = nudCTY.Value, ctZ = nudCTZ.Value;
			Vector3 cameraPosition = new Vector3((float)ctX, (float)ctY, (float)ctZ);
			Vector3 cameraTarget = new Vector3((float)cpX, (float)cpY, (float)cpZ);
			UpdateCameraPositionControls(cameraPosition);
			UpdateCameraTargetControls(cameraTarget);
			mCameraTarget = cameraTarget;
			mCameraPosition = cameraPosition;
			SetViewTransform();
		}

		private void cbChangeLightDirection_CheckedChanged(object sender, EventArgs e)
		{
			SetLightPositionAndDirection();
		}


	}
}
